#coding=utf8
import os
import sys
import re

from jinja2 import Template

from pake.chain_map import ChainMap
from pake.exceptions import *
from pake.utils import S, ColordLogger, render_string


class Line(object):
    """
    一行文本
    """
    __slots__ = ('lineno', 'text', 'source')

    def __init__(self, lineno, source):
        self.lineno = lineno
        self.source = source
        self.text = source

    def clone(self, text):
        inst = Line(self.lineno, self.source)
        inst.text = text
        return inst

    def __repr__(self):
        return '(L:{}) {}'.format(self.lineno, self.text)


class CommandParser(object):
    """
    指令段解析
    """
    pattern = '^(?P<name>\w+)(\((?P<argument>.*?)\))?\s*:\s*((?P<deps>.*?))?\s*$'

    def __init__(self, pakefile, signature, body, global_vars):
        self.pakefile = pakefile
        self.signature = signature
        self.body = body
        self.global_vars = global_vars

    def parse(self):
        matchobj = re.search(self.pattern, self.signature.text)
        if matchobj is None:
            raise CompileError(u'命令签名解析失败 [L:{}]: {}'.format(self.signature.lineno, self.signature.text))

        matchdict = matchobj.groupdict()
        name = matchdict['name']
        argument = matchdict['argument'] or ''
        deps = matchdict['deps'] or ''
        if not name:
            raise CompileError(u'命令名称缺失 [L:{}]: {}'.format(self.signature.lineno, self.signature.text))

        command = Command(self.pakefile, self.signature.lineno, name)
        command.body = self.body
        command.params = ArgumentParser(self.signature.clone(argument), self.global_vars).parse_parameter()
        command.deps = DependencesParser(self.signature.clone(deps), self.global_vars).parse()

        return command


class DependencesParser(object):
    """
    依赖解析
    """
    def __init__(self, line, global_vars):
        self.line = line
        self.global_vars = global_vars

    def parse(self):
        rv = []

        pos = 0

        deps_str = self.line.text.strip()
        while pos < len(deps_str):
            matchobj = re.search(r'^\s*(?P<name>\w+)(\((?P<argument>.*?)\))?', deps_str[pos:])
            if matchobj is None:
                raise CompileError('依赖声明解析错误 [L:{}]: {}'.format(self.line.lineno, self.line.text))

            matchdict = matchobj.groupdict()
            name = matchdict['name']
            if not name:
                raise CompileError('依赖名称缺失 [L:{}]: {}'.format(self.line.lineno, self.line.text))

            argument = matchdict['argument']
            bind_argument = ArgumentParser(self.line.clone(S(argument)), self.global_vars).parse_argument()
            rv.append(Dependence(name, bind_argument))

            pos += matchobj.end()

        return rv


class ArgumentParser(object):
    """
    形参、实参解析
    """
    def __init__(self, line, global_vars):
        self.line = line
        self.global_vars = global_vars

    def _parse(self):
        names = []
        values = []

        argument = self.line.text.strip()
        if len(argument) == 0:
            return names, values

        segments = argument.split(',')

        # 位置参数
        pos_arg_idx = 0
        for pos_arg in segments:
            if pos_arg.find('=') > -1:
                break
            else:
                names.append(pos_arg.strip())
                pos_arg_idx += 1

        # 关键字参数
        for kw_arg in segments[pos_arg_idx:]:
            try:
                k, v = kw_arg.split('=')
            except ValueError:
                raise CompileError(u'参数声明解析错误 [L:{}]: {}'.format(self.line.lineno, self.line.source))

            names.append(k.strip())
            values.append(render_string(v.strip().strip('\'"'), self.global_vars))

        return names, values

    def parse_parameter(self):
        names, values = self._parse()
        kwargs = dict(zip(names[len(names)-len(values):], values))
        return Parameter(names, kwargs)
    
    def parse_argument(self):
        names, values = self._parse()
        args = names[:len(names)-len(values)]
        kwargs = dict(zip(names[len(names)-len(values):], values))
        return Argument(args, kwargs)


class Parameter(object):
    """ 形参 """
    def __init__(self, names, kwargs):
        self.names = names
        self.kwargs = kwargs

    def __repr__(self):
        return '<names:{}, kwargs:{}>'.format(self.names, self.kwargs)

    def bind(self, cli_argument, bind_kwargs, parent_kwargs, global_vars):
        """参数绑定，返回一个dict"""
        rv = {}

        pos_args_len = len(cli_argument.args)

        for idx in range(pos_args_len):
            rv[self.names[idx]] = cli_argument.args[idx]

        cli_kwargs = {} if cli_argument is None else cli_argument.kwargs
        kwargs = ChainMap(cli_kwargs, bind_kwargs, parent_kwargs, self.kwargs, global_vars)

        for name in self.names[pos_args_len:]:
            try:
                rv[name] = kwargs[name]
            except KeyError:
                raise ArgumentError(u'无法匹配参数 {}, kwargs={}'.format(name, kwargs))

        return rv


class Argument(object):
    """ 实参 """
    def __init__(self, args, kwargs):
        self.args = args
        self.kwargs = kwargs
    
    def __repr__(self):
        return '<args:{}, kwargs:{}>'.format(self.args, self.kwargs)


class Dependence(object):
    """依赖"""
    def __init__(self, name, bind_argument):
        self.command_name = name
        self.bind_argument = bind_argument
    
    def __repr__(self):
        return '<{name}, {kwargs}>'.format(name=self.command_name, kwargs=self.bind_argument.kwargs)


class Command(object):
    def __init__(self, pakefile, lineno, name):
        self.pakefile = pakefile

        self.lineno = lineno
        self.name = name
        self.params = None
        self.deps = [] # 依赖列表，[Dependence, ]
        self.body = ''
        self.executed = False  # 是否已经执行过
    
    def __repr__(self):
        return '<{name}, {arg_names}, {kwargs}: {deps}]>{body}'.format(
            name=self.name, 
            arg_names=self.params.names, 
            kwargs=self.params.kwargs, 
            deps=self.deps, 
            body=self.body
        )

    def execute(self, cli_argument, bind_argument, parent_vars):
        """
        cli_argument: 启动参数
        bind_argument: 绑定参数
        parent_vars: 补充参数
        """
        if self.executed:
            return 
        
        bind_kwargs = {} if bind_argument is None else bind_argument.kwargs
        try:
            kwargs = self.params.bind(cli_argument, bind_kwargs, parent_vars, self.pakefile.global_vars)
        except ArgumentError as e:
            ColordLogger.error(u'{}(L:{}) 参数错误：{}'.format(self.name, self.lineno, e.message))
            sys.exit(-1)

        self._exec_deps(kwargs)

        ColordLogger.debug('[env][%s] cli_argument=%s', self.name, cli_argument)
        ColordLogger.debug('[env][%s] bind_argument=%s', self.name, bind_argument)
        ColordLogger.debug('[env][%s] signature_argument=%s', self.name, self.params)
        ColordLogger.debug('[env][%s] global_vars=%s', self.name, self.pakefile.global_vars)
        ColordLogger.debug('[env][%s] kwargs=%s', self.name, kwargs)
        self._exec_self(kwargs)
        self.executed = True
    
    def _exec_deps(self, kwargs):
        for dep in self.deps:
            self.pakefile.execute_command(name=dep.command_name, cli_argument=Argument((), {}), bind_argument=dep.bind_argument, global_vars=kwargs)

    def _exec_self(self, kwargs):
        """
        参数优先级：
        1. 启动参数: pos, kw
        2. 绑定参数: kw
        3. 签名参数*: kw
        4. global_vars: kw
        """
        if not self.body:
            self.exec_('')
            return

        context = dict(kwargs)
        for k, v in self.pakefile.global_vars.items():
            context.setdefault(k, v)

        body = '\n'.join(line.text for line in self.body)
        try:
            cmd = render_string(body, context)
        except:
            raise ExecuteError(u'命令 {} 格式化失败，context={}'.format(self.name, context))

        for row in cmd.split('\n'):
            self.exec_(row.strip())

    def exec_(self, cmd):
        stage = 'test' if self.pakefile.test else 'exec'
        ColordLogger.info('[%s][%s] %s', stage, self.name, cmd or 'NOP')
        if not self.pakefile.test:
            os.system(cmd)
